﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using Pixysoft.Framework.Verifications;

namespace Pixysoft.Framework.Reflection.Controller
{
    //http://forums.silverlight.net/forums/p/123922/286800.aspx
    //http://www.rainsts.net/article.asp?id=353
    //customer attribute 也建立
    static class DynamicProxyFactory
    {
        private const string module = "Pixysoft.Framework.Reflection.ProxyModule";

        private const string assembly = "Pixysoft.Framework.Reflection.ProxyAssembly";

        static AssemblyBuilder assemblyBuilder = null;

        static ModuleBuilder moduleBuilder = null;

        public static object CreateProxy(Type interfaceType)
        {
            if (!interfaceType.IsInterface)
                throw new Exception("pojo must be inherit from interface.");


            // create assembly

            assemblyBuilder = GetAssemblyBuilder();

            moduleBuilder = GetModuleBuilder(assemblyBuilder);



            //create type

            TypeBuilder typeBuilder = null;

            if (ReflectionHelper.IsInheritFrom(interfaceType, typeof(ICloneable)))
            {
                typeBuilder = GetTypeBuilder(moduleBuilder, interfaceType,
                    typeof(DynamicTransparentProxyBase),
                    new Type[] { interfaceType });
            }
            else
            {
                typeBuilder = GetTypeBuilder(moduleBuilder, interfaceType,
                    typeof(DynamicTransparentProxyBase),
                    new Type[] { interfaceType, typeof(ICloneable) });
            }


            // attribute

            EmitHelper.CreateInterfaceAttributes(interfaceType, typeBuilder);

            EmitHelper.CreateXmlRootAttribute(interfaceType, typeBuilder);

            EmitHelper.CreateSerializableAttribute(interfaceType, typeBuilder);


            // property

            List<string> duplicateProperties = new List<string>();

            foreach (PropertyInfo property in Pixysoft.Reflection.ReflectHelper.Instance.GetInterfaceProperties(interfaceType))
            {
                string propertyName = property.Name.Trim();

                if (duplicateProperties.Contains(propertyName))
                    continue;

                duplicateProperties.Add(propertyName);

                PropertyBuilder propertyBuilder = CreateProperty(typeBuilder, property);

                CreatePropertyAttributes(propertyBuilder, property);
            }

            foreach (MethodInfo method in Pixysoft.Reflection.ReflectHelper.Instance.GetInterfaceMethods(interfaceType))
            {
                if (Pixysoft.Reflection.ReflectHelper.Instance.IsPropertyMethod(method))
                    continue;

                if (ReflectionHelper.IsCloneMethod(method))
                    continue;

                MethodBuilder mbuilder = null;

                if (method.ReturnType.Equals(typeof(void)))
                {
                    mbuilder = CreateVoidMethod(typeBuilder, method);
                }
                else
                {
                    mbuilder = CreateReturnMethod(typeBuilder, method);
                }

                CreateMethodAttributes(mbuilder, method);
            }

            //clone method

            EmitHelper.CreateCloneMethod(typeBuilder);

            Type newPojoType = typeBuilder.CreateType();

            return Activator.CreateInstance(newPojoType);
        }

        private static AssemblyBuilder GetAssemblyBuilder()
        {
            if (assemblyBuilder != null)
                return assemblyBuilder;

            AppDomain currentAppDomain = AppDomain.CurrentDomain;

            AssemblyName assemblyName = new AssemblyName();

            assemblyName.Name = assembly;

            assemblyBuilder = currentAppDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            return assemblyBuilder;
        }

        private static ModuleBuilder GetModuleBuilder(AssemblyBuilder assemblyBuilder)
        {
            if (moduleBuilder != null)
                return moduleBuilder;

            moduleBuilder = assemblyBuilder.DefineDynamicModule(module);

            return moduleBuilder;
        }

        private static TypeBuilder GetTypeBuilder(ModuleBuilder moduleBuilder, Type proxyType, Type parentType, Type[] interfaceTypes)
        {
            //类名不能以数字开头 否则序列化是出错

            if (proxyType.IsGenericType)
                throw Exceptions.UnexpectedResultException("do not support generic type in pojo factory.");

            if (string.IsNullOrEmpty(proxyType.Name))
                throw Exceptions.UnexpectedResultException("unknown parent typename in pojo factory. type:{0}", proxyType.Name);

            String newTypeName = "_" + proxyType.Name + "_" + Guid.NewGuid().ToString().Replace("-", "_");

            if (parentType == null)
                parentType = typeof(object);

            TypeBuilder typeBuilder = moduleBuilder.DefineType(newTypeName,
                TypeAttributes.Class | TypeAttributes.NotPublic, parentType, interfaceTypes);

            return typeBuilder;
        }

        private static PropertyBuilder CreateProperty(TypeBuilder typeBuilder, PropertyInfo property)
        {
            MethodAttributes attribute =
                MethodAttributes.Public |
                MethodAttributes.FamANDAssem |
                MethodAttributes.Family |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig |
                MethodAttributes.SpecialName;

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.HasDefault, property.PropertyType, null);

            //.maxstack 5
            //.locals init (
            //    [0] class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodCallMessage inmsg,
            //    [1] class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage outmsg,
            //    [2] int32 CS$1$0000)
            //L_0000: nop 
            //L_0001: ldarg.0 

            //L_0002: ldarg.0 
            //L_0003: ldstr "get_Age"
            //L_0008: ldsfld class [mscorlib]System.Type[] [mscorlib]System.Type::EmptyTypes
            //L_000d: ldnull 
            //L_000e: call instance class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodCallMessage [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::CreateMethodCallMessage(object, string, class [mscorlib]System.Type[], object[])
            //L_0013: stloc.0 

            //L_0014: ldarg.0 
            //L_0015: ldloc.0 
            //L_0016: call instance class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::TryInvokeProxy(class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodCallMessage)
            //L_001b: stloc.1 

            //L_001c: ldarg.0 
            //L_001d: ldloc.1 
            //L_001e: call instance void [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::TryThrowException(class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage)
            //L_0023: nop 

            //L_0024: ldarg.0 
            //L_0025: ldloc.1 
            //L_0026: call instance object [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::TryReturnMethod(class [Pixysoft.Framework.Reflection]Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage)
            //L_002b: unbox.any int32
            //L_0030: stloc.2 

            //L_0031: br.s L_0033
            //L_0033: ldloc.2 
            //L_0034: ret 


            if (property.GetGetMethod() != null)
            {
                MethodBuilder getMethod = typeBuilder.DefineMethod("get_" + property.Name, attribute, property.PropertyType, Type.EmptyTypes);
                foreach (ParameterInfo parameter in property.GetGetMethod().GetParameters())
                    getMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

                ILGenerator getIl = getMethod.GetILGenerator();
                getIl.DeclareLocal(typeof(IDynamicMethodCallMessage));
                getIl.DeclareLocal(typeof(IDynamicMethodReturnMessage));

                OpCodesFactory.LdArgument(getIl, 0);

                OpCodesFactory.LdArgument(getIl, 0);
                OpCodesFactory.LdString(getIl, "get_" + property.Name);
                getIl.Emit(OpCodes.Ldsfld, typeof(Type).GetField("EmptyTypes"));
                OpCodesFactory.LdNull(getIl);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.CreateMethodCallMessageAttribute>(getIl);
                OpCodesFactory.SaveLocalVariable(getIl, 0);

                OpCodesFactory.LdArgument(getIl, 0);
                OpCodesFactory.LdLocalVariable(getIl, 0);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryInvokeProxyAttribute>(getIl);
                OpCodesFactory.SaveLocalVariable(getIl, 1);

                OpCodesFactory.LdArgument(getIl, 0);
                OpCodesFactory.LdLocalVariable(getIl, 1);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryThrowExceptionAttribute>(getIl);
                OpCodesFactory.Donothing(getIl);

                OpCodesFactory.LdArgument(getIl, 0);
                OpCodesFactory.LdLocalVariable(getIl, 1);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryReturnMethodAttribute>(getIl);
                OpCodesFactory.UnboxIfNeeded(getIl, property.PropertyType);
                //getIl.Emit(OpCodes.Castclass, property.PropertyType);//2010-09-12 bug!!!

                OpCodesFactory.ReturnMethod(getIl);
                propertyBuilder.SetGetMethod(getMethod);
            }

            //L_0000: nop 
            //L_0001: ldarg.0 
            //L_0002: ldarg.0 
            //L_0003: ldstr "set_Name"
            //L_0008: ldc.i4.1 
            //L_0009: newarr [mscorlib]System.Type
            //L_000e: stloc.2 
            //L_000f: ldloc.2 
            //L_0010: ldc.i4.0 
            //L_0011: ldtoken string
            //L_0016: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
            //L_001b: stelem.ref 
            //L_001c: ldloc.2 
            //L_001d: ldc.i4.1 
            //L_001e: newarr object
            //L_0023: stloc.3 
            //L_0024: ldloc.3 
            //L_0025: ldc.i4.0 
            //L_0026: ldarg.1 
            //L_0027: stelem.ref 
            //L_0028: ldloc.3 
            //L_0029: call instance class Pixysoft.Framework.Reflection.IDynamicMethodCallMessage Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::CreateMethodCallMessage(object, string, class [mscorlib]System.Type[], object[])
            //L_002e: stloc.0 
            //L_002f: ldarg.0 
            //L_0030: ldloc.0 
            //L_0031: call instance class Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::TryInvokeProxy(class Pixysoft.Framework.Reflection.IDynamicMethodCallMessage)
            //L_0036: stloc.1 
            //L_0037: ldarg.0 
            //L_0038: ldloc.1 
            //L_0039: call instance void Pixysoft.Framework.Reflection.DynamicTransparentProxyBase::TryThrowException(class Pixysoft.Framework.Reflection.IDynamicMethodReturnMessage)
            //L_003e: nop 
            //L_003f: ret 

            if (property.GetSetMethod() != null)
            {
                MethodBuilder setMethod = typeBuilder.DefineMethod("set_" + property.Name, attribute, null, new Type[] { property.PropertyType });
                foreach (ParameterInfo parameter in property.GetSetMethod().GetParameters())
                    setMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

                ILGenerator setIl = setMethod.GetILGenerator();
                setIl.DeclareLocal(typeof(IDynamicMethodCallMessage));
                setIl.DeclareLocal(typeof(IDynamicMethodReturnMessage));
                setIl.DeclareLocal(typeof(Type[]));
                setIl.DeclareLocal(typeof(object[]));
                OpCodesFactory.LdArgument(setIl, 0);

                OpCodesFactory.LdArgument(setIl, 0);
                OpCodesFactory.LdString(setIl, "set_" + property.Name);
                OpCodesFactory.CreateArray(setIl, typeof(Type), 1);
                OpCodesFactory.SaveLocalVariable(setIl, 2);
                OpCodesFactory.LdLocalVariable(setIl, 2);
                OpCodesFactory.LdIntOrEnum(setIl, 0);
                OpCodesFactory.LdType(setIl, property.PropertyType);
                setIl.Emit(OpCodes.Stelem_Ref);
                OpCodesFactory.LdLocalVariable(setIl, 2);
                OpCodesFactory.CreateArray(setIl, typeof(object), 1);
                OpCodesFactory.SaveLocalVariable(setIl, 3);
                OpCodesFactory.LdLocalVariable(setIl, 3);
                OpCodesFactory.LdIntOrEnum(setIl, 0);
                OpCodesFactory.LdArgument(setIl, 1);
                OpCodesFactory.BoxIfNeeded(setIl, property.PropertyType);
                setIl.Emit(OpCodes.Stelem_Ref);
                OpCodesFactory.LdLocalVariable(setIl, 3);

                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.CreateMethodCallMessageAttribute>(setIl);
                OpCodesFactory.SaveLocalVariable(setIl, 0);
                OpCodesFactory.LdArgument(setIl, 0);
                OpCodesFactory.LdLocalVariable(setIl, 0);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryInvokeProxyAttribute>(setIl);
                OpCodesFactory.SaveLocalVariable(setIl, 1);
                OpCodesFactory.LdArgument(setIl, 0);
                OpCodesFactory.LdLocalVariable(setIl, 1);
                OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryThrowExceptionAttribute>(setIl);
                OpCodesFactory.ReturnMethod(setIl);
                propertyBuilder.SetSetMethod(setMethod);
            }

            return propertyBuilder;
        }

        private static MethodBuilder CreateReturnMethod(TypeBuilder typeBuilder, MethodInfo method)
        {
            MethodAttributes attribute =
                MethodAttributes.Public |
                MethodAttributes.FamANDAssem |
                MethodAttributes.Family |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig |
                MethodAttributes.SpecialName;

            List<Type> parameterTypes = new List<Type>();

            foreach (ParameterInfo parameter in method.GetParameters())
                parameterTypes.Add(parameter.ParameterType);

            MethodBuilder getMethod = typeBuilder.DefineMethod(method.Name, attribute, method.ReturnType, parameterTypes.ToArray());
            foreach (ParameterInfo parameter in method.GetParameters())
                getMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);

            ILGenerator getMethodIl = getMethod.GetILGenerator();
            getMethodIl.DeclareLocal(typeof(IDynamicMethodCallMessage));
            getMethodIl.DeclareLocal(typeof(IDynamicMethodReturnMessage));
            getMethodIl.DeclareLocal(typeof(Type[]));
            getMethodIl.DeclareLocal(typeof(object[]));
            OpCodesFactory.LdArgument(getMethodIl, 0);//CreateMethodCallMessage start
            OpCodesFactory.LdArgument(getMethodIl, 0);//this
            OpCodesFactory.LdString(getMethodIl, method.Name);//methodname
            OpCodesFactory.CreateArray(getMethodIl, typeof(Type), parameterTypes.Count);// create types[]{}
            OpCodesFactory.SaveLocalVariable(getMethodIl, 2);
            for (int i = 0; i < parameterTypes.Count; i++)//如果用foreach 会产生传入数组为null
            {
                OpCodesFactory.LdLocalVariable(getMethodIl, 2);
                OpCodesFactory.LdIntOrEnum(getMethodIl, i);
                OpCodesFactory.LdType(getMethodIl, parameterTypes[i]);
                getMethodIl.Emit(OpCodes.Stelem_Ref);
            }
            OpCodesFactory.LdLocalVariable(getMethodIl, 2);// types[]
            OpCodesFactory.CreateArray(getMethodIl, typeof(object), parameterTypes.Count);//create object[]
            OpCodesFactory.SaveLocalVariable(getMethodIl, 3);
            for (int i = 0; i < parameterTypes.Count; i++)
            {
                OpCodesFactory.LdLocalVariable(getMethodIl, 3);
                OpCodesFactory.LdIntOrEnum(getMethodIl, i);
                OpCodesFactory.LdArgument(getMethodIl, i + 1);
                OpCodesFactory.BoxIfNeeded(getMethodIl, parameterTypes[i]);
                getMethodIl.Emit(OpCodes.Stelem_Ref);
            }
            OpCodesFactory.LdLocalVariable(getMethodIl, 3);// object[]
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.CreateMethodCallMessageAttribute>(getMethodIl);
            OpCodesFactory.SaveLocalVariable(getMethodIl, 0);
            OpCodesFactory.LdArgument(getMethodIl, 0);
            OpCodesFactory.LdLocalVariable(getMethodIl, 0);
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryInvokeProxyAttribute>(getMethodIl);
            OpCodesFactory.SaveLocalVariable(getMethodIl, 1);
            OpCodesFactory.LdArgument(getMethodIl, 0);
            OpCodesFactory.LdLocalVariable(getMethodIl, 1);
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryThrowExceptionAttribute>(getMethodIl);

            OpCodesFactory.Donothing(getMethodIl);
            OpCodesFactory.LdArgument(getMethodIl, 0);
            OpCodesFactory.LdLocalVariable(getMethodIl, 1);
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryReturnMethodAttribute>(getMethodIl);
            OpCodesFactory.UnboxIfNeeded(getMethodIl, method.ReturnType);
            //getMethodIl.Emit(OpCodes.Castclass, method.ReturnType);
            OpCodesFactory.ReturnMethod(getMethodIl);

            return getMethod;
        }

        private static MethodBuilder CreateVoidMethod(TypeBuilder typeBuilder, MethodInfo method)
        {
            MethodAttributes attribute =
                MethodAttributes.Public |
                MethodAttributes.FamANDAssem |
                MethodAttributes.Family |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig |
                MethodAttributes.SpecialName;

            List<Type> parameterTypes = new List<Type>();

            foreach (ParameterInfo parameter in method.GetParameters())
                parameterTypes.Add(parameter.ParameterType);

            MethodBuilder setMethod = typeBuilder.DefineMethod(method.Name, attribute, null, parameterTypes.ToArray());
            foreach (ParameterInfo parameter in method.GetParameters())
                setMethod.DefineParameter(parameter.Position + 1, ParameterAttributes.None, parameter.Name);
            //http://stackoverflow.com/questions/2183529/reason-why-methodbuilder-defineparameter-could-not-set-parameter-name

            ILGenerator setIl = setMethod.GetILGenerator();
            setIl.DeclareLocal(typeof(IDynamicMethodCallMessage));
            setIl.DeclareLocal(typeof(IDynamicMethodReturnMessage));
            setIl.DeclareLocal(typeof(Type[]));
            setIl.DeclareLocal(typeof(object[]));
            OpCodesFactory.LdArgument(setIl, 0);//CreateMethodCallMessage start
            OpCodesFactory.LdArgument(setIl, 0);//this
            OpCodesFactory.LdString(setIl, method.Name);//methodname
            OpCodesFactory.CreateArray(setIl, typeof(Type), parameterTypes.Count);// create types[]{}
            OpCodesFactory.SaveLocalVariable(setIl, 2);
            for (int i = 0; i < parameterTypes.Count; i++)//如果用foreach 会产生传入数组为null
            {
                OpCodesFactory.LdLocalVariable(setIl, 2);
                OpCodesFactory.LdIntOrEnum(setIl, i);
                OpCodesFactory.LdType(setIl, parameterTypes[i]);
                setIl.Emit(OpCodes.Stelem_Ref);
            }
            OpCodesFactory.LdLocalVariable(setIl, 2);// types[]
            OpCodesFactory.CreateArray(setIl, typeof(object), parameterTypes.Count);//create object[]
            OpCodesFactory.SaveLocalVariable(setIl, 3);
            for (int i = 0; i < parameterTypes.Count; i++)
            {
                OpCodesFactory.LdLocalVariable(setIl, 3);
                OpCodesFactory.LdIntOrEnum(setIl, i);
                OpCodesFactory.LdArgument(setIl, i + 1);
                OpCodesFactory.BoxIfNeeded(setIl, parameterTypes[i]);
                setIl.Emit(OpCodes.Stelem_Ref);
            }
            OpCodesFactory.LdLocalVariable(setIl, 3);// object[]
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.CreateMethodCallMessageAttribute>(setIl);
            OpCodesFactory.SaveLocalVariable(setIl, 0);
            OpCodesFactory.LdArgument(setIl, 0);
            OpCodesFactory.LdLocalVariable(setIl, 0);
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryInvokeProxyAttribute>(setIl);
            OpCodesFactory.SaveLocalVariable(setIl, 1);
            OpCodesFactory.LdArgument(setIl, 0);
            OpCodesFactory.LdLocalVariable(setIl, 1);
            OpCodesFactory.CallMethod<DynamicTransparentProxyBase, DynamicTransparentProxyBase.TryThrowExceptionAttribute>(setIl);
            OpCodesFactory.ReturnMethod(setIl);

            return setMethod;
        }





        private static void CreatePropertyAttributes(PropertyBuilder propertyBuilder, PropertyInfo method)
        {
            foreach (CustomAttributeData attributeData in CustomAttributeData.GetCustomAttributes(method))
            {
                List<object> paraList = new List<object>();

                foreach (CustomAttributeTypedArgument arg in attributeData.ConstructorArguments)
                {
                    paraList.Add(arg.Value);
                }

                CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(attributeData.Constructor, paraList.ToArray());

                propertyBuilder.SetCustomAttribute(attrBuilder);
            }
        }

        private static void CreateMethodAttributes(MethodBuilder mbuilder, MethodInfo method)
        {
            foreach (CustomAttributeData attributeData in CustomAttributeData.GetCustomAttributes(method))
            {
                List<object> paraList = new List<object>();

                foreach (CustomAttributeTypedArgument arg in attributeData.ConstructorArguments)
                {
                    paraList.Add(arg.Value);
                }

                CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(attributeData.Constructor, paraList.ToArray());

                mbuilder.SetCustomAttribute(attrBuilder);
            }
        }
    }
}
